上一篇中写了一个基本的回显服务器,最基本的功能是有了,但是并不够健壮,那么如何对它进行改进呢?我们需要考虑以下几种情况。
增加socket函数的错误处理
之前的程序中,使用的socket相关的api都没有进行错误判断,一旦某个函数发生错误,程序可能就会崩溃,所以我们需要给原生api包裹一层,添加错误判断,就像下面这样:1
2
3
4
5
6int Socket(int family, int type, int protocol){
int n;
if ( (n = socket(family, type, protocol)) < 0)
err_sys("socket error");
return(n);
}
其他的函数可以仿照这个分别写wrap包裹函数。
改写read/write函数
当read和write用在字节流套接字上时和读写普通的文件不太一样,read或write的字节数量可能会比实际的少。原因是内核中用于套接字的缓冲区已经达到极限,所以我们可能需要多次调用read/write才能完成I/O。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21ssize_t readn(int fd, void *vptr, size_t n){
size_t nleft;
ssize_t nread;
char *ptr;
ptr = (char *)vptr;
nleft = n;
while (nleft > 0) {
if ( (nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0; /* and call read() again */
else
return(-1);
} else if (nread == 0)
break; /* EOF */
nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}
僵死进程的处理
服务端进程在检测到一个连接后,就fork一个子进程来处理这个连接。
当客户端程序关闭后,系统就会检测到,然后会关闭该程序打开的所有描述符,然后给服务器发送一个FIN。
服务端fork的子进程接收到FIN后,以ACK响应。(此时服务器套接字处于CLOSE_WAIT状态,客户端套接字处于FIN_wait_2状态)
(可以通过netstat -a 命令来查看所有套接字的状态)
服务端子进程响应完ACK后,会给父进程发送一个SIGCHLD中断,但是该信号默认的行为是忽略,所以子进程就会进入僵死状态。
如果这种僵死进程特别多,就会占用大量资源,所以我们得处理这种情况。
(处理僵死进程,涉及到UNIX的信号处理,需要先对此有个了解。)
我们需要给服务端父进程添加对SIGCHLD信号的处理,其中有wait和waitpid两个函数可以用来终止子进程。
如果使用第一个的话,还有种情况会出问题,就是如果客户端一次发起了5个连接,然后客户端进程被关闭,然后服务端的5个子进程会几乎同时收到这个消息,然后同时给父进程发送5个SIGCHLD信号。
由于wait没有排队机制,只能处理一个,其他几个子进程仍可能会变成僵死进程。所以需要使用waitpid,需要如下三步操作:
1.编写wrap函数Signal():1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19Sigfunc * signal(int signo, Sigfunc *func){
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM) {
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
} else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
}
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
2.在accept前调用处理信号的函数:1
Signal(SIGCHLD,sig_chld);
3.编写sig_chld函数:1
2
3
4
5
6
7
8void sig_chld(int signo){
pid_t pid;
int stat;
while( (pid=waitpid(-1,&stat,WNOHANG)) > 0){
std::cout<<"child "<<pid<<" terminated"<<std::endl;
}
return ;
}
客户服务器之间传递二进制结构
之前的代码都是传送字符串,不会有什么问题。但在不同的系统上,字节序可能不一样,所以传送二进制数据时(比如int型数值)可能无法得到正确的数据。
第一个常用的方法是把数值转为文本串来传递。第二种是显式地指出所支持的数据类型的二进制格式,包括位数,大端或小端。
其他问题
除了以上几个问题,以下几个问题现在还无法解决,需要学习其他知识后才能来解决。
- 三路握手建立连接后,客户TCP发送了一个RST复位
- 在两者正常通信时,服务器子进程被杀死,这时候客户端正阻塞在fgets函数上,无法马上作出反应
- 服务器子进程被杀死后,服务器主机会给客户端发送FIN,然后客户端会关闭对应套接字,这时候客户端进程并不知道,如果继续向该套接字写入内容,那么会收到系统发出的SIGPIPE信号(默认操作是杀死进程)。
- 服务器主机崩溃时(不是进程崩溃,也不是执行关机命令)。
- 服务器主机崩溃后重启,此时再收到客户端发送的信息,会给客户端返回RST,然后导致正阻塞在redline的客户返回ECONNRESET错误。
- 服务器主机关机,客户端应当能立马知道(跟服务器子进程被杀死时类似)
目前三个文件内容如下
wrapfun.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
typedef void Sigfunc(int);
void err_sys(const char * str){
std::cout<<str<<std::endl;
return ;
}
int Socket(int family, int type, int protocol)
{
int n;
if ( (n = socket(family, type, protocol)) < 0)
err_sys("socket error");
return(n);
}
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
int n;
again:
if ( (n = accept(fd, sa, salenptr)) < 0) {
if (errno == EPROTO || errno == ECONNABORTED)
if (errno == ECONNABORTED)
goto again;
else
err_sys("accept error");
}
return(n);
}
void Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
if (bind(fd, sa, salen) < 0)
err_sys("bind error");
}
void Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
if (connect(fd, sa, salen) < 0)
err_sys("connect error");
}
void Listen(int fd, int backlog)
{
char *ptr;
/*4can override 2nd argument with environment variable */
if ( (ptr = getenv("LISTENQ")) != NULL)
backlog = atoi(ptr);
if (listen(fd, backlog) < 0)
err_sys("listen error");
}
ssize_t /* Read "n" bytes from a descriptor. */
readn(int fd, void *vptr, size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr;
ptr = (char *)vptr;
nleft = n;
while (nleft > 0) {
if ( (nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0; /* and call read() again */
else
return(-1);
} else if (nread == 0)
break; /* EOF */
nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}
/* end readn */
ssize_t
Readn(int fd, void *ptr, size_t nbytes)
{
ssize_t n;
if ( (n = readn(fd, ptr, nbytes)) < 0)
err_sys("readn error");
return(n);
}
ssize_t /* Write "n" bytes to a descriptor. */
writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = (const char *)vptr;
nleft = n;
while (nleft > 0) {
if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR)
nwritten = 0; /* and call write() again */
else
return(-1); /* error */
}
nleft -= nwritten;
ptr += nwritten;
}
return(n);
}
/* end writen */
void
Writen(int fd, void *ptr, size_t nbytes)
{
if (writen(fd, ptr, nbytes) != nbytes)
err_sys("writen error");
}
ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
ptr = (char*)vptr;
for (n = 1; n < maxlen; n++) {
again:
if ( (rc = read(fd, &c, 1)) == 1) {
*ptr++ = c;
if (c == '\n')
break; /* newline is stored, like fgets() */
} else if (rc == 0) {
*ptr = 0;
return(n - 1); /* EOF, n - 1 bytes were read */
} else {
if (errno == EINTR)
goto again;
return(-1); /* error, errno set by read() */
}
}
*ptr = 0; /* null terminate like fgets() */
return(n);
}
/* end readline */
ssize_t
Readline(int fd, void *ptr, size_t maxlen)
{
ssize_t n;
if ( (n = readline(fd, ptr, maxlen)) < 0)
err_sys("readline error");
return(n);
}
Sigfunc * signal(int signo, Sigfunc *func){
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM) {
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
} else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
}
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
/* end signal */
Sigfunc *
Signal(int signo, Sigfunc *func) /* for our signal() function */
{
Sigfunc *sigfunc;
if ( (sigfunc = signal(signo, func)) == SIG_ERR)
err_sys("signal error");
return(sigfunc);
}echoserv.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
using namespace std;
void str_echo(int sockfd){
ssize_t n;
char buf[MAXLINE];
again:
while((n=read(sockfd,buf,MAXLINE))>0){
Writen(sockfd,buf,n);
}
if(n<0 && errno==EINTR){
goto again;
}else if(n<0){
cout<<"str_echo: read error"<<endl;
}
}
void sig_chld(int signo){
pid_t pid;
int stat;
while( (pid=waitpid(-1,&stat,WNOHANG)) > 0){
std::cout<<"child "<<pid<<" terminated"<<std::endl;
}
return ;
}
int main(){
int listenfd,connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr,servaddr;
listenfd=Socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
Bind(listenfd,(SA*)&servaddr,sizeof(servaddr));
Listen(listenfd,LISTENQ);
Signal(SIGCHLD,sig_chld);
int i=0;
for(;;){
clilen=sizeof(cliaddr);
connfd=Accept(listenfd,(SA*)&cliaddr,&clilen);
if((childpid=fork())==0){
close(listenfd);
str_echo(connfd);
exit(0);
}
close(connfd);
i++;
cout<<i<<" "<<endl;
}
return 0;
}echoclie.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using namespace std;
int main(int argc,char **argv){
int i,sockfd[1005];
struct sockaddr_in servaddr;
if(argc!=2){
cout<<"usage:echoclie <ip addr>"<<endl;
return 0;
}
for(i=0;i<10;i++){
sockfd[i]=Socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(SERV_PORT);
inet_pton(AF_INET,argv[1],&servaddr.sin_addr);
Connect(sockfd[i],(SA*)&servaddr,sizeof(servaddr));
}
str_cli(stdin,sockfd[0]);
return 0;
}
void str_cli(FILE *fp,int sockfd){
char sendline[MAXLINE],recvline[MAXLINE];
while(fgets(sendline,MAXLINE,fp) != NULL){
Writen(sockfd,sendline,strlen(sendline));
//if(readline(sockfd,recvline,MAXLINE)==0){
if(Readline(sockfd,recvline,MAXLINE)<0){
cout<<"str_cli:server terminated prematurely"<<endl;
}
fputs(recvline,stdout);
}
}
参考
- 《UNIX网络编程(卷一第三版)》
欢迎与我分享你的看法。
转载请注明出处:http://taowusheng.cn/